﻿namespace Centido.Core.Test
{
    using System;
    using System.Data.Common;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Testing business units
    /// </summary>
    [TestClass]
    public class UnitTest
    {
        /// <summary>
        /// Domain name
        /// </summary>
        public const string DomainName = "FullAdminRights";

        /// <summary>
        /// Domain
        /// </summary>
        private Domain _domain;

        /// <summary>
        /// Test initialize
        /// </summary>
        [TestInitialize]
        public void TestInitialize()
        {
            _domain = Domain.Build(DomainConfiguration.Load(DomainName));
            _domain.HasRecycleBin = new Random(Environment.TickCount).Next() % 2 == 1;

            _domain.DeleteAllUnits();
        }

        /// <summary>
        /// Test cleanup
        /// </summary>
        [TestCleanup]
        public void TestCleanup()
        {
            _domain.DeleteAllUnits();
        }

        /// <summary>
        /// Creating a unit
        /// </summary>
        [TestMethod]
        public void CreatingUnit()
        {
            Assert.AreEqual(_domain.Units.Count(), 0);

            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            Assert.IsNotNull(unit);
            Assert.AreEqual(_domain.Units.Count(), 1);
            Assert.AreEqual(_domain.Units.Count(u => u.Id == unit.Id), 1);
            Assert.IsNotNull(_domain.GetUnit(unit.Id));
            Assert.IsNotNull(_domain.GetUnit(unit.Name));
            Assert.AreEqual(_domain.GetUnit(unit.Id).Id, unit.Id);
            Assert.AreEqual(_domain.GetUnit(unit.Name).Name, unit.Name);
            Assert.IsTrue(unit.Exists());
        }

        /// <summary>
        /// Creating unit specifying id
        /// </summary>
        [TestMethod]
        public void CreatingUnitWithId()
        {
            var unitId = Guid.NewGuid();
            var unitName = StringProperty.RandomIdentifier();

            var unit = _domain.AddUnit(unitId, unitName);

            Assert.IsNotNull(unit);
            Assert.AreEqual(unit.Id, unitId);
            Assert.AreEqual(unit.Name, unitName.ToLower());
        }

        /// <summary>
        /// Creating unit with duplicate id
        /// </summary>
        [TestMethod]
        public void CreatingUnitWithDuplicateId()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            try
            {
                _domain.AddUnit(unit.Id, StringProperty.RandomIdentifier());
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.AddingUnitWithDuplicateId);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        ///   Creating units with the same name.
        /// </summary>
        [TestMethod]
        public void CreatingUnitsWithDuplicateName()
        {
            _domain.AddUnit("first");

            try
            {
                _domain.AddUnit("first");
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.AddingUnitWithDuplicateName);
                return;
            }

            Assert.Fail();
        }


        /// <summary>
        ///   Checking that the unit name is trimmed and lowered when creating a unit.
        /// </summary>
        [TestMethod]
        public void CheckingThatUnitNameIsTrimmedAndLoweredWhenCreatingUnit()
        {
            var unit = _domain.AddUnit("  My_UNIT ");

            Assert.AreEqual(unit.Name, "my_unit");
        }


        /// <summary>
        ///   Reading and writing the unit name.
        /// </summary>
        [TestMethod]
        public void ReadingAndWritingUnitName()
        {
            var unitName = StringProperty.RandomIdentifier();
            var unit = _domain.AddUnit(unitName);

            Assert.AreEqual(unit.Name, unitName.ToLower());

            // Setting the unit name
            unitName = StringProperty.RandomIdentifier();

            unit.Name = unitName;

            Assert.AreEqual(unit.Name, unitName.ToLower());
        }


        /// <summary>
        ///   Setting too long unit name.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void SettingTooLongUnitName()
        {
            _domain.AddUnit(StringProperty.TooLongIdentifier('A'));
        }


        /// <summary>
        ///   Setting a duplicate unit name.
        /// </summary>
        [TestMethod]
        public void SettingDuplicateUnitName()
        {
            var firstUnit = _domain.AddUnit("my_unit");
            firstUnit.Name = "my_unit_2";

            var secondUnit = _domain.AddUnit("my_unit_3");

            try
            {
                secondUnit.Name = "my_unit_2";
            }
            catch (DbException)
            {
                Assert.AreEqual(secondUnit.Name, "my_unit_3");
                return;
            }

            Assert.Fail();
        }


        /// <summary>
        ///   Checking trimming and lowering when setting a unit name.
        /// </summary>
        [TestMethod]
        public void CheckingTrimmingAndLoweringWhenSettingUnitName()
        {
            var unit = _domain.AddUnit("my_unit");

            unit.Name = " UNIT_NAME ";

            Assert.AreEqual(unit.Name, "unit_name");
        }


        /// <summary>
        ///   Checking trimming and lowering when searching by the unit name.
        /// </summary>
        [TestMethod]
        public void CheckingTrimmingAndLoweringWhenSearchingByUnitName()
        {
            _domain.AddUnit("my_unit");

            var unit = _domain.GetUnit(" MY_UNIT ");

            Assert.IsNotNull(unit);
        }


        /// <summary>
        ///   Creating units with different names.
        /// </summary>
        [TestMethod]
        public void CreatingUnitWithDifferentNames()
        {
            _domain.AddUnit("first");
            _domain.AddUnit("second");

            Assert.AreEqual(_domain.Units.Count(), 2);
        }


        /// <summary>
        /// Reading and writing display name
        /// </summary>
        [TestMethod]
        public void ReadingAndWritingUnitDisplayName()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            Assert.IsNull(unit.DisplayName);

            //Setting unit display name
            var unitDisplayName = StringProperty.StandardValue('A');

            unit.DisplayName = unitDisplayName;

            Assert.AreEqual(unit.DisplayName, unitDisplayName);

            //Setting display name to null
            unit.DisplayName = null;

            Assert.IsNull(unit.DisplayName);
        }


        /// <summary>
        /// Setting too long display name
        /// </summary>
        [TestMethod]
        public void SettingTooLongDisplayName()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            try
            {
                unit.DisplayName = StringProperty.TooLongStandardValue('A');
            }
            catch (DbException)
            {
                Assert.IsNull(unit.DisplayName);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Building unit hierarchy
        /// </summary>
        [TestMethod]
        public void BuildingUnitHierarchy()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            Assert.AreEqual(unit.Parents.Count(), 0);
            Assert.AreEqual(unit.ChildUnits.Count(), 0);

            // Adding a child
            var child = _domain.AddUnit(StringProperty.RandomIdentifier());

            Assert.IsFalse(unit.ContainsUnit(child));
            Assert.IsFalse(child.ContainsUnit(unit));

            unit.AddUnit(child);

            Assert.AreEqual(child.Parents.Count(), 1);
            Assert.AreEqual(child.Parents.Count(p => p.Id == unit.Id), 1);
            Assert.AreEqual(unit.ChildUnits.Count(), 1);
            Assert.AreEqual(unit.ChildUnits.Count(u => u.Id == child.Id), 1);
            Assert.IsTrue(unit.ContainsUnit(child));
            Assert.IsFalse(child.ContainsUnit(unit));

            // Adding another child
            var anotherChild = _domain.AddUnit(StringProperty.RandomIdentifier());

            Assert.IsFalse(unit.ContainsUnit(anotherChild));

            unit.AddUnit(anotherChild);

            Assert.AreEqual(anotherChild.Parents.Count(), 1);
            Assert.AreEqual(anotherChild.Parents.Count(p => p.Id == unit.Id), 1);
            Assert.AreEqual(unit.ChildUnits.Count(), 2);
            Assert.AreEqual(unit.ChildUnits.Count(u => u.Id == anotherChild.Id), 1);
            Assert.IsTrue(unit.ContainsUnit(anotherChild));

            // Adding another parent
            var anotherParent = _domain.AddUnit(StringProperty.RandomIdentifier());

            Assert.IsFalse(anotherParent.ContainsUnit(child));

            anotherParent.AddUnit(child);

            Assert.AreEqual(child.Parents.Count(), 2);
            Assert.AreEqual(child.Parents.Count(p => p.Id == anotherParent.Id), 1);
            Assert.AreEqual(anotherParent.ChildUnits.Count(), 1);
            Assert.AreEqual(anotherParent.ChildUnits.Count(u => u.Id == child.Id), 1);
            Assert.IsTrue(anotherParent.ContainsUnit(child));
        }

        /// <summary>
        ///   Adding a child unit to a non-existent parent.
        /// </summary>
        [TestMethod]
        public void AddingChildUnitToNonExistentParent()
        {
            var child = _domain.AddUnit(StringProperty.RandomIdentifier());
            var parent = _domain.AddUnit(StringProperty.RandomIdentifier());

            parent.Delete();

            try
            {
                parent.AddUnit(child);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.UnitWasNotFound);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        ///   Adding a non-existent child unit.
        /// </summary>
        [TestMethod]
        public void AddingNonExistentChildUnit()
        {
            var child = _domain.AddUnit(StringProperty.RandomIdentifier());
            var parent = _domain.AddUnit(StringProperty.RandomIdentifier());

            child.Delete();

            try
            {
                parent.AddUnit(child);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.UnitWasNotFound);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Adding duplicate child unit
        /// </summary>
        [TestMethod]
        public void AddingDuplicateChildUnit()
        {
            var child = _domain.AddUnit(StringProperty.RandomIdentifier());
            var parent = _domain.AddUnit(StringProperty.RandomIdentifier());

            parent.AddUnit(child);

            try
            {
                parent.AddUnit(child);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.AddingDuplicateUnitParent);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Passing null when adding child unit
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException), AllowDerivedTypes = true)]
        public void PassingNullWhenAddingChildUnit()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            unit.AddUnit(null);
        }

        /// <summary>
        /// Building cycle of one unit
        /// </summary>
        [TestMethod]
        public void BuildingCycleOfOneUnit()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            try
            {
                unit.AddUnit(unit);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.BuildingCycleOfUnits);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Building cycle of two units
        /// </summary>
        [TestMethod]
        public void BuildingCycleOfTwoUnits()
        {
            var firstUnit = _domain.AddUnit(StringProperty.RandomIdentifier());
            var secondUnit = _domain.AddUnit(StringProperty.RandomIdentifier());

            secondUnit.AddUnit(firstUnit);

            try
            {
                firstUnit.AddUnit(secondUnit);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.BuildingCycleOfUnits);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Building cycle of three units
        /// </summary>
        [TestMethod]
        public void BuildingCycleOfThreeUnits()
        {
            var firstUnit = _domain.AddUnit(StringProperty.RandomIdentifier());
            var secondUnit = _domain.AddUnit(StringProperty.RandomIdentifier());
            var thirdUnit = _domain.AddUnit(StringProperty.RandomIdentifier());

            firstUnit.AddUnit(secondUnit);
            secondUnit.AddUnit(thirdUnit);

            try
            {
                thirdUnit.AddUnit(firstUnit);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.BuildingCycleOfUnits);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Building hierarchy with duplicate parents
        /// </summary>
        [TestMethod]
        public void BuildingHierarchyWithDuplicateParents()
        {
            var unit1 = _domain.AddUnit(StringProperty.RandomIdentifier());
            var unit2 = _domain.AddUnit(StringProperty.RandomIdentifier());
            var unit3 = _domain.AddUnit(StringProperty.RandomIdentifier());
            var unit4 = _domain.AddUnit(StringProperty.RandomIdentifier());

            unit2.AddUnit(unit1);
            unit3.AddUnit(unit1);
            unit4.AddUnit(unit2);
            unit4.AddUnit(unit3);

            Assert.AreEqual(unit1.Parents.Count(), 2);
            Assert.AreEqual(unit2.Parents.Count(), 1);
            Assert.AreEqual(unit3.Parents.Count(), 1);
        }

        /// <summary>
        ///   Removing a unit child.
        /// </summary>
        [TestMethod]
        public void RemovingUnitChild()
        {
            var child = _domain.AddUnit(StringProperty.RandomIdentifier());
            var parent = _domain.AddUnit(StringProperty.RandomIdentifier());

            parent.AddUnit(child);

            Assert.AreEqual(child.Parents.Count(), 1);
            Assert.AreEqual(parent.ChildUnits.Count(), 1);
            Assert.IsTrue(parent.ContainsUnit(child));

            parent.RemoveUnit(child);

            Assert.AreEqual(child.Parents.Count(), 0);
            Assert.AreEqual(parent.ChildUnits.Count(), 0);
            Assert.IsFalse(parent.ContainsUnit(child));
        }

        /// <summary>
        ///   Passing null when removing a unit child.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException), AllowDerivedTypes = true)]
        public void PassingNullWhenDeletingUnitParent()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            unit.RemoveUnit(null);
        }

        /// <summary>
        ///   Removing a child from a non-existent unit.
        /// </summary>
        [TestMethod]
        public void RemovingChildUnitFromNonExistentUnit()
        {
            var child = _domain.AddUnit(StringProperty.RandomIdentifier());
            var parent = _domain.AddUnit(StringProperty.RandomIdentifier());

            parent.Delete();

            try
            {
                parent.RemoveUnit(child);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.UnitWasNotFound);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        ///   Removing a non-existent child unit.
        /// </summary>
        [TestMethod]
        public void RemovingNonExistentChildUnit()
        {
            var child = _domain.AddUnit(StringProperty.RandomIdentifier());
            var parent = _domain.AddUnit(StringProperty.RandomIdentifier());

            child.Delete();

            try
            {
                parent.RemoveUnit(child);
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.UnitWasNotFound);
                return;
            }

            Assert.Fail();
        }


        /// <summary>
        ///   Deleting a unit.
        /// </summary>
        [TestMethod]
        public void DeletingUnitByReference()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            Assert.AreEqual(_domain.Units.Count(), 1);

            //Deleting a unit
            _domain.DeleteUnit(unit);

            Assert.AreEqual(_domain.Units.Count(), 0);
            Assert.IsNull(_domain.GetUnit(unit.Id));
            Assert.IsFalse(unit.Exists());
        }

        /// <summary>
        /// Deleting unit by id
        /// </summary>
        [TestMethod]
        public void DeletingUnitById()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());
            unit.Name = StringProperty.RandomIdentifier();

            Assert.AreEqual(_domain.Units.Count(), 1);

            //Deleting a unit
            _domain.DeleteUnit(unit.Id);

            Assert.AreEqual(_domain.Units.Count(), 0);
            Assert.IsNull(_domain.GetUnit(unit.Id));
            Assert.IsNull(_domain.GetUnit(unit.Name));
            Assert.IsFalse(unit.Exists());
        }

        /// <summary>
        /// Checking that deleted unit is removed from children and parent units
        /// </summary>
        [TestMethod]
        public void CheckingThatDeletedUnitIsRemovedFromChildrenAndParentUnits()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());
            var child = _domain.AddUnit(StringProperty.RandomIdentifier());
            var parent = _domain.AddUnit(StringProperty.RandomIdentifier());

            parent.AddUnit(unit);
            unit.AddUnit(child);

            Assert.AreEqual(parent.ChildUnits.Count(), 1);
            Assert.AreEqual(child.Parents.Count(), 1);

            unit.Delete();

            Assert.AreEqual(parent.ChildUnits.Count(), 0);
            Assert.AreEqual(child.Parents.Count(), 0);
        }

        /// <summary>
        /// Deleting unit that was deleted
        /// </summary>
        [TestMethod]
        public void DeletingUnitThatWasDeleted()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            unit.Delete();

            try
            {
                unit.Delete();
            }
            catch (DbException e)
            {
                Assert.AreEqual(e.GetErrorCode(), DbErrorCode.UnitWasNotFound);
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        /// Changing properties of deleted unit
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(DbException), AllowDerivedTypes = true)]
        public void ChangingPropertiesOfDeletedUnit()
        {
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            //Setting unit display name
            unit.DisplayName = StringProperty.StandardValue('A');

            Assert.IsNotNull(unit.DisplayName);

            //Deleting the unit
            unit.Delete();

            unit.Refresh();

            Assert.IsNull(_domain.GetUnit(unit.Id));

            Assert.IsNull(unit.DisplayName);

            //Setting unit display name again
            unit.DisplayName = StringProperty.StandardValue('A');
        }


        /// <summary>
        ///   Searching unit by name.
        /// </summary>
        [TestMethod]
        public void SearchingUnitByName()
        {
            _domain.AddUnit(StringProperty.RandomIdentifier());
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            unit.Name = "first";

            Assert.IsNotNull(_domain.GetUnit("first"));
            Assert.IsNull(_domain.GetUnit("second"));
            Assert.AreEqual(_domain.GetUnit("first").Id, unit.Id); 
            Assert.AreEqual(_domain.GetUnit("first").Name, "first"); 
        }
    }
}